package ru.lukyanets.transmission.client.config;

import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import org.apache.commons.lang3.StringUtils;
import ru.lukyanets.transmission.client.connection.ConnectionInfo;
import ru.lukyanets.transmission.client.util.PasswordUtil;
import ru.lukyanets.transmission.protocol.client.builder.HostInfo;

import com.google.inject.Singleton;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;

/**
 * @author slukyanets
 */
@Singleton
public final class ConnectionConfig extends Config {
    private static final String NODE = "connection";
    private static final String INDEX_KEY = "index";
    private static final String HOST_KEY = "host";
    private static final String PORT_KEY = "port";
    private static final String USERNAME_KEY = "username";
    private static final String PASSWORD_KEY = "password";
    private static final String USE_SSL_KEY = "use_ssl";
    private static final String PROXY_HOST_KEY = "proxy_host";
    private static final String PROXY_PORT_KEY = "proxy_port";
    private static final String PROXY_USERNAME_KEY = "proxy_username";
    private static final String PROXY_PASSWORD_KEY = "proxy_password";
    private static final String DEFAULT_CONNECTION_KEY = "default_connection";

    private Map<String, ConnectionInfo> connections = new LinkedHashMap<>();
    private String defaultConnection;

    private ReadOnlyObjectWrapper<List<ConnectionInfo>> connectionsProperty = new ReadOnlyObjectWrapper<>();
    private ReadOnlyStringWrapper defaultConnectionProperty = new ReadOnlyStringWrapper();

    public ConnectionConfig() {
        super(NODE);

        try {
            Map<Integer, ConnectionInfo> tmp = new TreeMap<>();
            for (String name : getPreferences().childrenNames()) {
                Preferences node = getPreferences().node(name);
                int index = node.getInt(INDEX_KEY, 0);
                tmp.put(index, loadConnection(node));
            }
            tmp.values().forEach(c -> connections.put(c.getName(), c));
        } catch (BackingStoreException ignored) {
        }
        connectionsProperty.set(getConnections());

        defaultConnection = getPreferences().get(DEFAULT_CONNECTION_KEY, null);
        if (defaultConnection == null && connections.size() > 0) {
            defaultConnection = connections.keySet().iterator().next();
        }
        defaultConnectionProperty.set(defaultConnection);
    }

    private ConnectionInfo loadConnection(Preferences node) {
        ConnectionInfo connectionInfo = new ConnectionInfo(node.name());
        HostInfo host = new HostInfo();
        host.setHost(node.get(HOST_KEY,""));
        host.setPort(node.getInt(PORT_KEY, ConnectionInfo.DEFAULT_PORT));
        host.setUsername(node.get(USERNAME_KEY,""));
        host.setPassword(PasswordUtil.decrypt(node.getByteArray(PASSWORD_KEY, null)));
        connectionInfo.setHost(host);

        connectionInfo.setUseSsl(node.getBoolean(USE_SSL_KEY, false));

        String proxyHostName = StringUtils.trimToNull(node.get(PROXY_HOST_KEY, ""));
        if (proxyHostName != null) {
            HostInfo proxyHost = new HostInfo();
            proxyHost.setHost(proxyHostName);
            proxyHost.setPort(node.getInt(PROXY_PORT_KEY, ConnectionInfo.DEFAULT_PROXY_PORT));
            proxyHost.setUsername(node.get(PROXY_USERNAME_KEY, ""));
            proxyHost.setPassword(PasswordUtil.decrypt(node.getByteArray(PROXY_PASSWORD_KEY, null)));

            connectionInfo.setProxyHost(proxyHost);
        }

        return connectionInfo;
    }

    @Override
    public void store() {
        // remove all child nodes
        try {
            for (String n : getPreferences().childrenNames()) {
                getPreferences().node(n).removeNode();
            }
            getPreferences().clear();
        } catch (BackingStoreException e) {
            throw new IllegalStateException("Preferences storage in illegal state!", e);
        }
        int index = 0;
        for (ConnectionInfo connection : connections.values()) {
            storeConnection(index, connection, getPreferences().node(connection.getName()));
            index++;
        }

        getPreferences().put(DEFAULT_CONNECTION_KEY, defaultConnection != null ? defaultConnection : "");
    }

    private void storeConnection(int index, ConnectionInfo connection, Preferences node) {
        HostInfo host = connection.getHost();
        node.putInt(INDEX_KEY, index);
        //noinspection Duplicates
        if (host != null) {
            node.put(HOST_KEY, host.getHost());
            node.putInt(PORT_KEY, host.getPort());
            node.put(USERNAME_KEY, host.getUsername());
            node.putByteArray(PASSWORD_KEY, PasswordUtil.encrypt(host.getPassword()));
        }
        node.putBoolean(USE_SSL_KEY, connection.isUseSsl());

        HostInfo proxyHost = connection.getProxyHost();
        //noinspection Duplicates
        if(proxyHost != null) {
            node.put(PROXY_HOST_KEY, proxyHost.getHost());
            node.putInt(PROXY_PORT_KEY, proxyHost.getPort());
            node.put(PROXY_USERNAME_KEY, proxyHost.getUsername());
            node.putByteArray(PROXY_PASSWORD_KEY, PasswordUtil.encrypt(proxyHost.getPassword()));
        }
    }

    public ConnectionInfo getDefaultConnection() {
        return this.connections.get(defaultConnection);
    }

    public void setDefault(String defaultConnection) {
        if (connections.containsKey(defaultConnection)) {
            this.defaultConnection = defaultConnection;
            defaultConnectionProperty.set(defaultConnection);
        } else {
            throw new RuntimeException("Invalid argument");

        }
    }

    public List<ConnectionInfo> getConnections() {
        return connections.values().stream().map(ConnectionInfo::new).collect(Collectors.toList());
    }

    public ReadOnlyObjectProperty<List<ConnectionInfo>> getConnectionsProperty() {
        return connectionsProperty.getReadOnlyProperty();
    }

    public ReadOnlyStringProperty getDefaultConnectionProperty() {
        return defaultConnectionProperty.getReadOnlyProperty();
    }

    public void set(Collection<ConnectionInfo> connections) {
        this.connections.clear();
        connections.forEach(con -> this.connections.put(con.getName(), con));
        if (!this.connections.containsKey(defaultConnection)) {
            defaultConnection = this.connections.size() > 0 ? this.connections.keySet().iterator().next() : null;
        }
        connectionsProperty.set(getConnections());
    }

}
